from datetime import datetime

from my_io.disk import Disk, BLOCK_SIZE
from loguru import logger
import pandas as pd
from system.setting import *
from tools.commandtools import *
from tools.consts import *
from tools.validater import *


class File_system(object):
    def __init__(self):
        logger.remove()
        f = open(LOG_FILE_LOCATION, "w", encoding="utf-8")
        logger.add(sink=f)
        # 加载磁盘
        exists, self.disk = Disk.load_disk()
        logger.info("磁盘加载完成!")
        # 加载内存
        self.memory = Memory()
        logger.info("内存加载完成!")
        # 初始化信息
        self.user = None
        self.current_dir = self.disk.root
        self.current_path = ""
        # 挂载root目录
        if not exists:
            self.mount_root()
        logger.info("初始化完成!")

    def mount_root(self):
        # 挂载root目录
        root_inode = self.disk.get_new_inode()
        block_index = self.disk.get_new_block_index()
        root_inode.file_block_index.append(block_index)
        root_inode.file_type = DIR
        root_inode.file_create_time = datetime.now()
        root_inode.file_own = "root"
        root_inode.file_own_permission = READ_AND_WRITE
        root_inode.file_other_permission = ONLY_READ
        root_inode.file_size = 0
        root = Directory("root", root_inode)
        self.current_dir.mount(root, self.disk)
        parent = self.link(self.current_dir, "..")
        root.mount(parent, self.disk)

    def run(self):
        logger.info("文件系统启动!")
        while True:
            is_login = self.login()
            while is_login:
                print_tip(self.user, self.current_path)
                command = input("").strip()
                if command == "\n":
                    continue
                else:
                    command, args = process_command(command)
                    switch = {
                        "cd": self.cd,
                        "dir": self.dir,
                        "create": self.create,
                        "delete": self.delete,
                        "deldir": self.deldir,
                        "open": self.open,
                        "close": self.close,
                        "read": self.read,
                        "write": self.write,
                        "search": self.search,
                        "copy": self.copy,
                        "cut": self.cut,
                        "exit": self.exit
                    }
                    switch.get(command, self.no_such_command)(*args)

    def link(self, source_file, target_name):
        return Directory(target_name, source_file.inode)

    def exit(self, *args):
        Disk.dump_disk(self.disk)
        print("bye~")
        exit(0)

    def dir(self, *args):
        dir_list = self.list_dir(self.current_dir)
        output_dir(dir_list)

    def create(self, *args):
        if arg_num_validate(2, *args):
            result = self.create_new(args[1], self.current_dir, self.user, args[0])
            if not result[0]:
                print(result[1])
        else:
            if args[0] is None:
                print("expect two args,but get 0 args")
            else:
                print("expect two args,but get %s args" % len(args))

    def delete(self, *args):
        if arg_num_validate(1, *args):
            if self.current_dir.unmount(args[0], self.disk):
                print("OK")
            else:
                print("File not exist")
        else:
            if args[0] is None:
                print("expect one args,but get 0 args")
            else:
                print("expect one args,but get %s args" % len(args))

    def deldir(self, *args):
        if arg_num_validate(1, *args):
            if self.current_dir.unmount(args[0], self.disk):
                print("OK")
            else:
                print("Directory not exist")
        else:
            if args[0] is None:
                print("expect one args,but get 0 args")
            else:
                print("expect one args,but get %s args" % len(args))

    def close(self, *args):
        if arg_num_validate(1, *args):
            self.close_file(args[0])
        else:
            if args[0] is None:
                print("expect two args,but get 0 args")
            else:
                print("expect two args,but get %s args" % len(args))

    def open(self, *args):
        if arg_num_validate(1, *args):
            self.open_file(args[0])
        else:
            if args[0] is None:
                print("expect one args,but get 0 args")
            else:
                print("expect one args,but get %s args" % len(args))

    def read(self, *args):
        if arg_num_validate(1, *args):
            file = self.find_file_by_name(self.current_dir, args[0])
            if file is None or file.inode.file_type == DIR:
                print("file not exist")
            else:
                if self.memory.contain_file(file.filename) is not None:
                    result = ""
                    for block_index in file.inode.file_block_index:
                        block = self.disk.get_block_by_index(block_index)
                        if len(block.get_content()) != 0:
                            result += block.get_content()[0]
                    print(result, end="")
                else:
                    print("File not opened,Please open first!")
        else:
            if args[0] is None:
                print("expect one args,but get 0 args")
            else:
                print("expect one args,but get %s args" % len(args))

    def write(self, *args):
        if arg_num_validate(1, *args):
            file = self.find_file_by_name(self.current_dir, args[0])
            if file is None or file.inode.file_type == DIR:
                print("file not exist")
            else:
                if self.memory.contain_file(file.filename) is not None:
                    contents = ""
                    content = ""
                    print("please write the content:")
                    while True:
                        content = input()
                        if content == ":wq":
                            break
                        else:
                            contents = contents + content + "\n"
                    # 如果写入内容所需文件块数量大于原来的文件块数量，申请新的文件块。
                    if round(len(contents) / BLOCK_SIZE) > len(file.inode.file_block_index):
                        need_block_num = round(len(contents) / BLOCK_SIZE) - len(file.inode.file_block_index)
                        for i in range(need_block_num):
                            file.inode.file_block_index.append(self.disk.get_new_block_index())

                    for block_index in file.inode.file_block_index:
                        block = self.disk.get_block_by_index(block_index)
                        if block_index == file.inode.file_block_index[-1]:
                            block.write_content(contents[0:len(contents)], len(contents))
                        else:
                            block.write_content(contents[0:BLOCK_SIZE], BLOCK_SIZE)
                            contents = contents[BLOCK_SIZE:]
                    print("\nWrite  Successfully!")
                else:
                    print("File not opened,Please open first!")
        else:
            if args[0] is None:
                print("expect one args,but get 0 args")
            else:
                print("expect one args,but get %s args" % len(args))

    def search(self, *args):
        file = []
        if arg_num_validate(1, *args) or arg_num_validate(2, *args):
            if len(args) == 1:
                file = self.disk.root
                temp_path = ""
            else:
                temp_path = args[1]
                temp_args = self.is_path_exist(args[1])
                if temp_args[0]:
                    file = temp_args[1]
                else:
                    print("Path not exist!")
                    return
            for item in self.list_dir(file):
                if item.inode.file_type == FILE and item.filename == args[0]:
                    print(temp_path + "/" + item.filename)
                elif item.inode.file_type == DIR and item.dirname != "..":
                    temp = (args[0], temp_path + "/" + item.dirname)
                    self.search(*temp)
                else:
                    pass



        else:
            if args[0] is None:
                print("expect one or two args,but get 0 args")
            else:
                print("expect one or two args,but get %s args" % len(args))

    def is_path_exist(self, path):
        path = path.split("/")
        if path[0] == '':
            current_path = self.disk.root  # 根
            path.pop(0)
        else:
            current_path = self.current_dir
        for i in path:
            current_dir = current_path.find_file_by_name(i, self.disk)  # 对象
            if current_dir is None or current_dir.inode.file_type == FILE:
                return False, None
            else:
                current_path = current_dir
        return True, current_path

    def copy(self, *args):
        if arg_num_validate(2, *args):
            file = self.find_file_by_name(self.current_dir, args[0])
            if file is None or file.inode.file_type == DIR:
                print("file not exist")
                return False
            else:
                temp_file = self.is_path_exist(args[1])
                if self.find_file_by_name(temp_file[1], args[0]) is not None:
                    print("File exist or filename is same as dir name! you can't copy it!")
                    return False
                else:
                    if temp_file[0]:
                        temp_args = self.create_new(args[0], temp_file[1], self.user, FILE)
                        if temp_args[0]:
                            new_file = temp_args[1]
                            new_file.inode.file_own = file.inode.file_own
                            new_file.inode.file_own_permission = file.inode.file_own_permission
                            new_file.inode.file_create_time = file.inode.file_create_time
                            new_file.inode.file_size = file.inode.file_size
                            new_file.inode.file_other_permission = file.inode.file_other_permission
                            for index in new_file.inode.file_block_index:
                                self.disk.init_block_by_index(index)
                            new_file.inode.file_block_index = []
                            for index in file.inode.file_block_index:
                                new_block_index = self.disk.get_new_block_index()
                                new_file.inode.file_block_index.append(new_block_index)
                                self.disk.get_block_by_index(new_block_index).write_content(
                                    self.disk.get_block_by_index(index).get_content(),
                                    self.disk.get_block_by_index(index).get_size(), "list")
                            return True
                    else:
                        print("Path not exist!")
                        return False
        else:
            if args[0] is None:
                print("expect two args,but get 0 args")
            else:
                print("expect two args,but get %s args" % len(args))
            return False

    def cut(self, *args):
        if arg_num_validate(2, *args):
            file = self.find_file_by_name(self.current_dir, args[0])
            if file is None or file.inode.file_type == DIR:
                print("file not exist")
            else:
                if self.copy(*args):
                    self.delete(args[0])
        else:
            if args[0] is None:
                print("expect two args,but get 0 args")
            else:
                print("expect two args,but get %s args" % len(args))

    def login(self):
        users = pd.read_csv(USER_FILE_LOCATION)
        username = input("please input username:")
        password = input("password:")
        if users[users.username == username].empty:
            print("username is not exist!")
            return False
        else:
            if not (users[users.username == username].password == password).empty:
                print("Welcome! " + username)
                self.user = username
                self.cd(username)
                return True
            else:
                print("password is not correct")
                return False

    def create_new(self, filename, mounted_dir, owner, file_type):
        """
        创建一个新的文件, 步骤如下
        0. 检查是否存在该filename的文件
        1. 申请一个inode
        2. 申请一个未分配文件块, 并将bitmap对应索引值置为1
        3. 创建一个File对象, 并对其inode进行初始化赋值
        4. 将该文件挂载到当前目录下, 文件目录内容就是一个列表, 将新创建的对象加入即可
        :param file_type:
        :param filename 文件名
        :param mounted_dir 要挂载到的目录对象
        :param owner 文件拥有者
        :return File对象
        """
        if file_type not in [FILE, DIR]:
            print("args error!pleas input \"create file/dir filename/dirname\"")

        for file in self.list_dir(mounted_dir):
            if file.inode.file_type == FILE and file.filename == filename:
                logger.error("文件重名")
                return False, "文件重名"
            elif file.inode.file_type == DIR and file.dirname == filename:
                logger.error("文件重名")
                return False, "文件重名"

        logger.debug("无重名,开始创建文件.")
        inode = self.disk.get_new_inode()
        inode.file_own_permission = READ_AND_WRITE
        block_index = self.disk.get_new_block_index()
        if block_index == -1:
            logger.error("inode用完了")
            return False, "inode用完了"
        else:
            inode.file_block_index = [block_index]
            inode.file_create_time = datetime.now()
            inode.file_other_permission = ONLY_READ
            inode.file_own = owner
            inode.file_size = 0
            inode.file_type = file_type
            if file_type == FILE:
                new_file = File(filename, inode)
                mounted_dir.mount(new_file, self.disk)
            else:
                new_file = Directory(filename, inode)
                mounted_dir.mount(new_file, self.disk)
                parent = self.link(mounted_dir, "..")
                new_file.mount(parent, self.disk)
            return True, new_file

    def delete_file(self, filename, directory):
        """
        删除一个文件, 步骤如下
        0. 检查是否存在名为filename的文件
        1. 通过文件名找到该对象
        2. 将该对象占用的文件块进行释放
        3. 取消挂载
        :param filename 要删除的文件名
        :param directory 要删除的文件所在的目录
        :return 是否成功
        """
        pass

    def list_dir(self, directory) -> list:
        """
        目录对象理应有此方法, 因此系统接口中是否提供该接口有待考虑
        1. 通过inode找到所有物理块地址
        2. 读取出物理块的内容
        3. 返回
        :return:
        """
        file_list = []
        for item in directory.inode.file_block_index:
            file_list.extend(self.disk.get_block_by_index(item).get_content())
        return file_list

    def delete_dir(self, dirname, directory):
        """
        删除目录, 步骤和删除文件一样, 不过注意的是, 删除一个文件一般要删除其下所有文件, 需要递归删除
        """
        pass

    # def search_file(self, search_dir, search_filename):
    #     """
    #     在目标目录下搜索文件, 注意递归搜索并返回所有结果
    #     :param search_dir 要搜索的目录
    #     :param search_filename 要搜索的文件名
    #     :return 搜索到的文件所在路径
    #     """
    #     pass

    def change_dir(self, dirname):
        """
        进入到目标目录
        """
        pass

    def open_file(self, filename):
        """
        打开文件
        """
        file = self.find_file_by_name(self.current_dir, filename)
        if file is None:
            print("File not found")
            return
        elif file.inode.file_type == DIR:
            print("You can't open a directory")
            return
        else:
            self.memory.load(file)
            print("Open successfully")

    def close_file(self, filename):
        self.memory.dump(filename)

    # def read_file(self, filename):
    #     pass
    #
    # def write_file(self, filename, data):
    #     pass
    #
    # def copy_file(self, source_file, target):
    #     pass
    #
    # def cut_file(self, source_file, target):
    #     pass

    def find_file_by_name(self, find_dir, find_filename):
        """
        通过文件名查找文件
        :param find_dir: 查找的目录
        :param find_filename: 查找的文件
        :return:
        """
        for file in self.list_dir(find_dir):
            if file.inode.file_type == FILE and file.filename == find_filename:
                return file
            elif file.inode.file_type == DIR and file.dirname == find_filename:
                return file
        return None

    def no_such_command(self, *args):
        print("Command not found")

    def cd(self, *args):
        if arg_num_validate(1, *args):
            if args[0] == "/":
                self.current_dir = self.disk.root
                self.current_path = "/"
            else:
                for item in self.list_dir(self.current_dir):
                    if item.inode.file_type == DIR and item.dirname == args[0]:
                        self.current_dir = item
                        if args[0] == "..":
                            if self.current_path == "/":
                                return
                            else:
                                temp_path = self.current_path.split("/")
                                temp_path.pop()
                                if len(temp_path)==1 and temp_path[0] == "":
                                    self.current_path="/"
                                else:
                                    self.current_path = "/".join(temp_path)
                                return
                        else:
                            if self.current_path == "/":
                                self.current_path += item.dirname
                            else:
                                self.current_path += "/" + item.dirname
                            return
                print("No such directory!")
        else:
            if args[0] is None:
                print("expect one args,but get 0 args")
            else:
                print("expect one args,but get %s args" % len(args))


class File(object):
    def __init__(self, filename, inode):
        self.filename = filename
        self.inode = inode


class Directory(object):
    def __init__(self, dirname, inode):
        self.dirname = dirname
        self.inode = inode

    def find_file_by_name(self, filename, disk):
        for index in self.inode.file_block_index:
            for file in disk.get_block_by_index(index).get_content():
                if file.inode.file_type == FILE and file.filename == filename:
                    return file
                elif file.inode.file_type == DIR and file.dirname == filename:
                    return file
        return None

    def mount(self, file, disk):
        """
        挂载到目录下
        :param file: File对象
        :param disk: disk对象
        :return:
        """
        block = disk.get_block_by_index(self.inode.file_block_index[-1])
        block.append_content(file, file.inode.file_size)

    def unmount(self, filename, disk):
        """
        从目录反挂载，即从目录内容中删除
        步骤：
        1.读取目录内容
        2.根据文件名找到文件
        3.释放对应文件块 -->内容清空，修改位图
        4.释放inode，将inode放到unused_inode列表中
        5.从当前按目录删除
        :param filename:
        :param disk:
        :return:
        """
        for block_index in self.inode.file_block_index:
            for file in disk.get_block_by_index(block_index).get_content():
                if file.inode.file_type == FILE:
                    if file.filename == filename:
                        # 清除文件块内容
                        for index in file.inode.file_block_index:
                            disk.init_block_by_index(index)
                        # 回收inode
                        disk.release_inode(file.inode)
                        block_content = disk.get_block_by_index(block_index).get_content()
                        block_content.remove(file)
                        disk.get_block_by_index(block_index).write_content(block_content, 4 * len(block_content),
                                                                           "list")
                        return True
                else:
                    if file.dirname == filename:
                        # 清除文件块内容
                        for index in file.inode.file_block_index:
                            disk.init_block_by_index(index)
                        # 回收inode
                        disk.release_inode(file.inode)
                        block_content = disk.get_block_by_index(block_index).get_content()
                        block_content.remove(file)
                        disk.get_block_by_index(block_index).write_content(block_content, 4 * len(block_content),
                                                                           "list")
                        return True
        return False


class Inode(object):
    def __init__(self):
        # 4b
        self.file_type = None
        # 4b
        self.file_size = None
        # 3b
        self.file_own_permission = None
        # 3b
        self.file_other_permission = None
        # 10b
        self.file_create_time = None
        # 10b
        self.file_own = None
        # 4b
        self.file_block_index = []


class Memory(object):
    def __init__(self):
        self.content = []
        self.size = 0

    def load(self, file):
        if self.size + len(file.inode.file_block_index) > MEMORY_SIZE:
            print("Memory overflow, please close some files")
        else:
            self.content.append(file)
            self.size += len(file.inode.file_block_index)

    def dump(self, filename):
        file = self.contain_file(filename)
        if self.contain_file(filename) is None:
            print("File is not opened")
            logger.warning("File not found in memory")
        else:
            self.content.remove(file)
            print("Close successfully")

    def contain_file(self, filename):
        for i in self.content:
            if i.inode.file_type == FILE and i.filename == filename:
                return i
            elif i.inode.file_type == DIR and i.dirname == filename:
                return i
        return None
